﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace ADOWrapper.ObjectResolver
{
    class GenericPopulator<T> : IObjectPopulator<T>
    {

        static object initializerLocker = new object();


        static Tuple<string, Action<object, T>>[] setters = null;
        IInstanceCreator<T> instanceCreator;



        static GenericPopulator()
        {
            InitializeCreateSetters();
        }

        public GenericPopulator()
            : this(null)
        { }

        public GenericPopulator(IInstanceCreator<T> instanceCreator)
        {
            this.instanceCreator = new GenericInstanceCreator<T>();

        }

        static private void InitializeCreateSetters()
        {
            BindingFlags modifierAccess = BindingFlags.Public | BindingFlags.Instance;
            var propertyInfos = typeof(T).GetProperties(modifierAccess)
                .Where(x => x.CanWrite && x.GetSetMethod() != null)
                .Select(x => new { Name = x.Name, Type = x.PropertyType, DataItemAttribute = x.GetCustomAttributes(typeof(DataItemAttribute), true).FirstOrDefault() as DataItemAttribute });


            //var fieldInfo = typeof(T).GetFields(modifierAccess | BindingFlags.SetField)
            //    .Where(x => (x.Attributes & FieldAttributes.InitOnly) != FieldAttributes.InitOnly && !x.IsPrivate)
            //    .Select(x => new { Name = x.Name, Type = x.FieldType, DataItemAttribute = x.GetCustomAttributes(typeof(DataItemAttribute), true).FirstOrDefault() as DataItemAttribute });

            var settersInfo = propertyInfos
                //.Concat(fieldInfo)
                .Where(x => x.DataItemAttribute != null ? !x.DataItemAttribute.Ignore : true).ToArray();

            setters = new Tuple<string, Action<object, T>>[settersInfo.Length];

            for (int i = 0; i < settersInfo.Length; i++)
            {
                var info = settersInfo[i];
                var ex = CreatePropertySetter(info.Name, info.Type);
                string dataItemName = info.DataItemAttribute != null ? (info.DataItemAttribute.Name) : info.Name;
                setters[i] = Tuple.Create<string, Action<object, T>>(dataItemName, ex.Compile());
            }
        }


        //private Expression CreateFieldExpression(FieldInfo fieldInfo)
        //{
        //    var objectParameterExpression = Expression.Parameter(typeof(T));
        //    return Expression.Field(objectParameterExpression, fieldInfo);
        //}

        //private Expression CreatePropertyExpression(PropertyInfo propertyInfo)
        //{
        //    var objectParameterExpression = Expression.Parameter(typeof(T));
        //    return Expression.Property(objectParameterExpression, propertyInfo);


        //}



        static private Expression<Action<object, T>> CreatePropertySetter(string propertyName, Type propertyType)
        {

            var instanceParam = Expression.Parameter(typeof(T), "populatedObjectInstance");
            var propertyFieldExpression = Expression.PropertyOrField(instanceParam, propertyName); ;
            var destinationType = propertyType;
            var genericConverterMethod = SimpleConverter.GetConvertMethodInfo(destinationType);

            var inputParam = Expression.Parameter(typeof(object), "value");
            var callConverter = Expression.Call(genericConverterMethod, inputParam);


            Expression<Action<object, T>> ex = Expression.Lambda<Action<object, T>>(
                Expression.Assign(propertyFieldExpression, callConverter),
               inputParam, instanceParam);

            return ex;

        }

        public T Populate(System.Data.IDataRecord record)
        {
            T populatedObject = this.instanceCreator.Resolve();

            for (int i = 0; i < setters.Length; i++)
            {
                var obj = record[setters[i].Item1];
                setters[i].Item2(obj, populatedObject);

            }
            return populatedObject;
        }
    }
}
